#include <arch/types.h>
#include <arch/CPU.h>
#include <arch/Paging.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

#define ReloadPageDirectory()	CPU::WriteCR3(CPU::ReadCR3())

PageDirectory Paging::kPgDir;

dword PageDirectory::Virtual2Physical(dword virt) {
	PageTable *tbl = Tables[PAGEDIR_ENTRY(virt)];
	
	if (!tbl)
		return NULL;
	
	return tbl->Pages[PAGETAB_ENTRY(virt)].frame << 12;
}

int PageDirectory::MapPage(word mode, dword virt, dword phys) {
	PageTable *tbl = Tables[PAGEDIR_ENTRY(virt)];
	
	if (!tbl) {
		Tables[PAGEDIR_ENTRY(virt)] = (PageTable *) valloc(sizeof(PageTable));
		tbl = Tables[PAGEDIR_ENTRY(virt)];
		ASSERT(tbl != NULL);
		memset(tbl, 0, sizeof(PageTable));
		TablesPhys[PAGEDIR_ENTRY(virt)] = Virtual2Physical((dword) tbl);
	}
	
	tbl->Pages[PAGETAB_ENTRY(virt)].frame = phys >> 12;
	tbl->Pages[PAGETAB_ENTRY(virt)].present = 1;
	tbl->Pages[PAGETAB_ENTRY(virt)].rw = (mode & PAGE_FLAGS_WRITE) ? 1 : 0;
	tbl->Pages[PAGETAB_ENTRY(virt)].user = (mode & PAGE_FLAGS_USER) ? 1 : 0;
	
	if (CPUs[0]->PageDir == this) {
		ReloadPageDirectory();
	}
	
	return 0;
}

int PageDirectory::UnmapPage(dword virt) {
	struct PageTable *tbl = Tables[PAGEDIR_ENTRY(virt)];
	
	if (!tbl) {
		return -1;
	}

	memset(&tbl->Pages[PAGETAB_ENTRY(virt)], 0, sizeof(Page));
	
	if (CPUs[0]->PageDir == this) {
		ReloadPageDirectory();
	}
	
	return 0;
}

int PageDirectory::MapMemory(word mode, dword virt, dword phys, dword len) {
	for (dword i = virt; i < virt + len; i += 0x1000) {
		MapPage(mode, i, phys);
		phys += 0x1000;
	}
	
	return 0;
}

int PageDirectory::UnmapMemory(dword virt, dword len) {
	for (dword i = virt; i < virt + len; i += 0x1000) {
		UnmapPage(i);
	}
	
	return 0;
}

extern "C" PageTable *kernelPageDirectory[256];

void Paging::Init() {
	kPgDir.PhysicalAddress = (dword) &kPgDir.TablesPhys - 0xC0000000;
	
	for (int i = 0; i < 1024; i++) {
		if (((dword) kernelPageDirectory[i] & 0x1FF) == 0x83) {
			kPgDir.Tables[i] = kernelPageDirectory[i];
			kPgDir.TablesPhys[i] = (dword) kernelPageDirectory[i] - 0xC0000000;
		}
		else {
			kPgDir.Tables[i] = NULL;
			kPgDir.TablesPhys[i] = NULL;
		}
	}
}
